package com.xiu.travel.config;

import com.xiu.travel.component.CustomCacheResolver;
import com.xiu.travel.handler.IgnoreExceptionCacheErrorHandler;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * redis cache 配置类
 *
 * @author : qianlanyuyun
 * @since : 2023/3/27 13:52
 */

@Configuration
@EnableCaching
@EnableConfigurationProperties(RedisProperties.class)
@Slf4j
@SuppressWarnings("all")
public class RedisCachingConfig implements CachingConfigurer
{

    @Autowired
    private RedisConnectionFactory connectionFactory;

    /**
     * cacheManager
     *
     * @return RedisCacheManager RedisCacheManager
     */
    @Bean
    public CacheManager redisCacheManager()
    {
        RedisSerializationContext.SerializationPair<Object> serializationPair =
                RedisSerializationContext.SerializationPair.fromSerializer(getRedisSerializer());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                                                                                 .entryTtl(Duration.ofSeconds(60 * 5))
                                                                                 .serializeValuesWith(serializationPair);
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }


    @Bean
    public CacheManager cacheManagerWithCacheLoading()
    {
        log.info("cacheManagerWithCacheLoading");
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                                                    .initialCapacity(100)
                                                    .maximumSize(10000)
                                                    .refreshAfterWrite(60 * 5, TimeUnit.SECONDS)
                                                    .expireAfterWrite(60 * 5, TimeUnit.SECONDS);

        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCacheLoader(cacheLoader());
        cacheManager.setAllowNullValues(true);
        cacheManager.setCaffeine(caffeine);
        cacheManager.setCacheNames(getNames());
        return cacheManager;
    }

    /**
     * 必须要指定这个Bean，refreshAfterWrite配置属性才生效
     */
    @Bean
    public CacheLoader<Object, Object> cacheLoader()
    {
        return new CacheLoader<Object, Object>()
        {
            @Override
            public Object load(Object key) throws Exception { return null;}

            // 重写这个方法将oldValue值返回回去，进而刷新缓存
            @Override
            public Object reload(Object key, Object oldValue) throws Exception
            {
                System.out.println("--refresh--:" + key);
                return oldValue;
            }
        };
    }

    /**
     * CacheResolver
     *
     * @return CacheResolver CacheResolver
     */
    @Bean
    @Override
    public CacheResolver cacheResolver()
    {
        // 通过Caffeine实现的自定义堆内存缓存管理器
        CacheManager caffeineCacheManager = cacheManagerWithCacheLoading();
        CacheManager redisCacheManager = redisCacheManager();
        List<CacheManager> list = new ArrayList<>();
        // 优先读取堆内存缓存
        list.add(caffeineCacheManager);
        // 堆内存缓存读取不到该key时再读取redis缓存
        list.add(redisCacheManager);
        return new CustomCacheResolver(list);
    }

    /**
     * KeyGenerator
     *
     * @return KeyGenerator KeyGenerator
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator()
    {
        return (target, method, params) ->
        {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for ( Object obj : params
            )
            {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }

    /**
     * CacheErrorHandler
     *
     * @return CacheErrorHandler CacheErrorHandler
     */
    @Bean
    @Override
    public CacheErrorHandler errorHandler()
    {
        return new IgnoreExceptionCacheErrorHandler();
    }

    private RedisSerializer<Object> getRedisSerializer()
    {
        return new GenericFastJsonRedisSerializer();
    }

    private static List<String> getNames()
    {
        List<String> names = new ArrayList<>(2);
        names.add("xiu");
        return names;
    }
}
